use std::fs::File;
use std::sync::Arc;
use std::io::Read;
use std::ops::Range;
use std::time::Instant;

use util::{UnsafeBuf,Cp,MakeCp};
use parking_lot::{Condvar,Mutex};

#[cfg(feature = "avx")]
use crate::superfeature::SplitCollector;
#[cfg(feature = "avx")]
use crate::superfeature::RabinWindowAvx8;
#[cfg(feature = "avx")]
use pipelines::Sender;
#[cfg(feature = "avx")]
use crate::superfeature::rabin_chunking;
#[cfg(feature = "avx")]
use super::ChunkerProduct;

use crate::backup::{BackupError, BackupResult};
use crate::constants::*;

use super::split::CHUNKING;

pub struct Block {
	path:String,
	len:usize,
	cursor:Mutex<usize>,
	buf:Arc<UnsafeBuf>,
}

unsafe impl Send for Block {}
unsafe impl Sync for Block {}

impl Block {
	pub fn new(path:String,len:usize) -> Self {
		Self {
			path:path,
			len:len,
			cursor:Mutex::new(0),
			buf:Arc::new(UnsafeBuf::new(len)),
		}
	}

	pub fn file_size(&self) -> usize {
		self.len
	}

	pub fn path(&self) -> &String {
		&self.path
	}
	
	pub fn cursor(&self) -> usize {
		*self.cursor.lock()
	}
	
	pub fn move_cursor(&self,next_cursor:usize) {
		let mut curr_cusor = self.cursor.lock();
		*curr_cusor = next_cursor;
	}
	
	pub fn read(&self,cond:&Condvar) -> BackupResult<u128> {
		let mut file = File::open(&self.path)
			.map_err(|e|BackupError::FileOpen(self.path.clone(), e))?; 
		let buf = unsafe { &mut *self.buf.get() };
		let mut cost_time:u128 = 0;
		loop {
			let curr_cursor = self.cursor();
			let next_cursor = self.len.min(curr_cursor+BACKUP_READ_BUF_LEN);
			if curr_cursor == next_cursor { break; }
			let now = Instant::now();
			file.read(&mut buf[curr_cursor..next_cursor])
				.map_err(|e|BackupError::FileRead(self.path.clone(), e))?;
			cost_time += now.elapsed().as_micros();
			self.move_cursor(next_cursor);
			cond.notify_all();
		}
		Ok(cost_time)
	}
	
	pub fn ok(&self,cond:&Condvar) -> Range<usize> {
		let mut file_cursor = self.cursor.lock();
		let buf_cursor = self.buf.cursor();
		if *file_cursor == self.len || *file_cursor - buf_cursor >= BACKUP_SPLIT_BUF_LEN {
			buf_cursor..*file_cursor
		} else {
			cond.wait(&mut file_cursor);
			buf_cursor..*file_cursor
		}
	}
	
	
	pub fn split(&self,cond:&Condvar) -> (Option<Cp<[u8]>>,u128) {
		let range = self.ok(cond);
		if range.is_empty() { return (None,0); }
		let now = Instant::now();
		let cut_len = unsafe { CHUNKING(&self.buf[range]) };
		let cost_time = now.elapsed().as_micros();
		(self.buf.make_const_slice(cut_len),cost_time)
	}

	#[cfg(feature = "avx")]
	pub fn split_rabin_avx8(&self,cond:&Condvar,window:&mut RabinWindowAvx8,collector:&mut SplitCollector,out:&Sender<ChunkerProduct>) -> Option<(u128,usize)> {
		let range = self.ok(cond);
		if range.is_empty() { return None; }
		let data = &self.buf[range];
		if data.len() < BACKUP_SPLIT_BUF_LEN {
			let now = Instant::now();
			let cut_len = rabin_chunking(data);
			let cp = self.buf.make_const_slice(cut_len);
			let cost_time = now.elapsed().as_micros();
			if cp.is_none() { return None; }
			out.send(ChunkerProduct::ChunkPointer(cp.unwrap()));
			return Some((cost_time,cut_len));
		}
		let now = Instant::now();
		unsafe {
			collector.refresh();
			window.refresh(data);
			loop {
				let result = window.split();
				if result != 0 {
					collector.push(result, window.cursor())
				} else {
					break;
				}
			}
		}
		let cost_time = now.elapsed().as_micros();
		let mut big_cut_len = 0;
		loop {
			if let Some(cut_len) = collector.pop() {
				let cp = self.buf.make_const_slice(cut_len);
				println!("cut_len {} cursor {}",cut_len,self.buf.cursor());
				if cp.is_none() { return None; }
				big_cut_len += cut_len;
				out.send(ChunkerProduct::ChunkPointer(cp.unwrap()));
			} else {
				break;
			}
		}
		Some((cost_time,big_cut_len))
	}
}
